﻿using System;
using System.Threading;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Net;
using System.ComponentModel;
using ModbusTCP;


namespace AppMain
{
#if false
    class TxPlcData
    {
        bool Heartbeat;
        bool Error;
        bool Heating;
        byte channel;
        byte power;
        byte modulation;
        bool HeaterOn;
        bool GeneralOn;
        bool GeneratorModulation;
        bool GeneratorPower;
        bool HeatOnForMinimumTime;
    }
#endif

    class TxPlcComm : System.Windows.Forms.Form
    {
        // Mutex protects PLC from accessing data which is in use
        public Mutex _txPlcWriteMutex = new Mutex();
        public Mutex _txPlcReadMutex = new Mutex();
        public string _guiText = "Initializing";
        public Color _guiTextForeColor = Color.Black;

        //public TxPlcData _dataFromPlc = new TxPlcData();
        public Dictionary<int, RequestToTx> _requests = new Dictionary<int, RequestToTx>();
        //PLC Address: C0-C1777 = 3072-4095
        //  Used for control: C300 = 3264
        //Note: Read and Write start addresses are the same so that the written data can be read again to verify communication integrity
        private ushort P_PlcReadAddress_us = 3264;
        private ushort P_PlcWriteAddress_us = 3264;

        //Data to read is twice as long as data to write, since it is good to verify the written data
        private bool[] g_PlcReadData_b = new bool[64];
        private bool[] g_PlcWriteData_b = new bool[32];
        private bool[] g_PlcOverrideData_b = new bool[32];
        private bool g_UserOverridesBurk_b = false;

        /*****************************************************************
         * Memory Mapping between Plc data and intended instructions     *
         ****************************************************************/
        private int tx6Offset_i = 32;
        private int tx7Offset_i = 48;
        public enum B_Write
        {
            Heartbeat = 0,
            ErrorTx6,
            HeatingOnTx6,
            ErrorTx7,
            HeatingOnTx7
        }
        public enum E_PlcEvent
        {
            DataReceived = 0,
            Error1,
            Error2
        }
        private enum B_TxRead
        {
            Ch1 = 0,
            Ch2,
            Ch4,
            Ch8,
            Pwr1,
            Pwr2,
            Mod1,
            Mod2,
            HeaterOn,
            GeneralOn,
            GeneratorModulation,
            GeneratorPower,
            HeatOnForMinimumTime
        }

        //Modbus variables
        public bool _commEnabled = false;
        public bool _commWasEnabled = false;
        private bool _failedConnection = false;
        public string PlcIpAddress { get; set; }
        public string GuiText { get; set; }

        private ModbusTCP.Master MBmaster;
        private bool _modbusCommPortOpen = false;
        private bool _modbusReadPending = false;
        private ushort C_ReadID = 1;
        private ushort C_WriteID = 2;

        //Create instance of log data for logging from this file
        AppMain.LogData log = AppMain.LogData.GetObject;

        //Declaration for Application to respond to PLC data received
        public System.Threading.Timer tmrScanPLC;
        private const int RetryTimeout = 5000;
        private int _scanTime;
        public delegate void PlcResponse(E_PlcEvent PlcEvent, string GuiText, Dictionary<int, RequestToTx> requests);

        public string GetStatusText()
        {
            return GuiText;
        }
        //Method called to start communication with the PLC, setting simple parameters
        public void StartCommunication(string IpAddress, int scanTime)
        {   //Set PLC communication parameters
            PlcIpAddress = IpAddress;
            //Create Timer for polling data from PLC
            tmrScanPLC = new System.Threading.Timer(tmrScanPLC_Tick,null,scanTime,scanTime);
            _scanTime = scanTime;
            //Enable cyclic read-write to PLC
            _commEnabled = true;
            _txPlcReadMutex.WaitOne();
            _requests.Clear();
            _requests.Add(6, new RequestToTx { Command = Thompson.Tx.E_CommandTx.Unknown, PowerRequest = 4, GenModulation = false, HeatOnForMinTime = false, ModulationRequest = 4, Channel = 17, GenPower = false });
            _requests.Add(7, new RequestToTx { Command = Thompson.Tx.E_CommandTx.Unknown, PowerRequest = 4, GenModulation = false, HeatOnForMinTime = false, ModulationRequest = 4, Channel = 17, GenPower = false });
            _txPlcReadMutex.ReleaseMutex();
            GuiText = "Initializing Communications...";
        }

        //Cyclic timer fetch new data from PLC and write response
        private void tmrScanPLC_Tick(object sender)
        {
            //If there was an error then stop here
            if (_commEnabled == false)
            {
                tmrScanPLC.Change(RetryTimeout,RetryTimeout);
            }
            else
            {
                if(!_commWasEnabled)
                {
                    tmrScanPLC.Change(_scanTime,_scanTime);
                }
            }
            _commWasEnabled = _commEnabled;
            //Open Modbus if not already connected
            if (!_modbusCommPortOpen)
            {
                // Note: This timer runs in a separate thread. There is no need for any
                // unique operations here.
                // keep this from connecting muliple ports
                if (!_modbusReadPending)
                {
                    connect();
                }
            }
            else
            {
                if (!MBmaster.connected)
                {
                    _modbusCommPortOpen = false;
                }
                if (_modbusReadPending == false)
                {
                    //run method ReadWrite() in a background thread
                    //System.Threading.Thread ReadWriteThread = new System.Threading.Thread(ReadWrite);
                    //ReadWriteThread.Start();
                    //Don't start if we're still waiting for a response
                    ReadWrite();
                }
            }
        }

        // Handle information reported back from the PLC. This should Read responses (valid data) and
        // Write responses (validity indication).
        private void OnPlcResponse(TxPlcComm.E_PlcEvent PlcEvent, string GuiText)
        {
            //Update display status
            _guiText = GuiText;

            if (PlcEvent != TxPlcComm.E_PlcEvent.DataReceived)
            {   //Set color for error
                _guiTextForeColor = Color.Red;
            }
            else
            {
                _guiTextForeColor = Color.Black;
                _guiText = "Successfully Communicating";
            }
        }

        //Master communication method which sends and receives data with PLC over Modbus
        //This method is only called when the connection has been successfully made and
        //the program is ready to communicate with the PLC
        private void ReadWrite()
        {
            //Toggle Heartbeat bit
            g_PlcWriteData_b[Convert.ToInt32(B_Write.Heartbeat)] = !g_PlcWriteData_b[Convert.ToInt32(B_Write.Heartbeat)];

            //Write Coils
            //Convert bit array into byte array
            if (g_UserOverridesBurk_b == true)
            {
                //Now we want to write g_PlcWriteData_b[] and g_PlcOverrideData_b (special case)
                var l_PlcWriteBytes = new byte[(g_PlcWriteData_b.Length / 8) + (g_PlcOverrideData_b.Length / 8) + 1];
                BitArray bitArray1 = new BitArray(g_PlcWriteData_b);
                BitArray bitArray2 = new BitArray(g_PlcOverrideData_b);
                bitArray1.CopyTo(l_PlcWriteBytes, 0);
                bitArray2.CopyTo(l_PlcWriteBytes, g_PlcWriteData_b.Length / 8);

                //Write Coils using Modbus
                MBmaster.WriteMultipleCoils(C_WriteID, P_PlcWriteAddress_us, (ushort)(g_PlcWriteData_b.Length + g_PlcOverrideData_b.Length), l_PlcWriteBytes);

                //reset override flag
                g_UserOverridesBurk_b = false;
            }
            else
            {
                //We only want to write g_PlcWriteData_b[] (normal case)
                var l_PlcWriteBytes = new byte[(g_PlcWriteData_b.Length / 8) + 1];
                BitArray bitArray = new BitArray(g_PlcWriteData_b);
                bitArray.CopyTo(l_PlcWriteBytes, 0);

                //Write Coils using Modbus
                MBmaster.WriteMultipleCoils(C_WriteID, P_PlcWriteAddress_us, (ushort)g_PlcWriteData_b.Length, l_PlcWriteBytes);
            }

            //Read Coils
            ushort Length = (ushort)g_PlcReadData_b.Length;
            MBmaster.ReadCoils(C_ReadID, P_PlcReadAddress_us, Length);
            _modbusReadPending = true; //Request is sent; new data is pending
        }

        public void WriteHeating(int txNum, bool state)
        {
            /************************************
             * Public function to write data to PLC
             ************************************/
            if (txNum == 6) g_PlcWriteData_b[Convert.ToInt32(B_Write.HeatingOnTx6)] = state;
            else if (txNum == 7) g_PlcWriteData_b[Convert.ToInt32(B_Write.HeatingOnTx7)] = state;
        }

        public void WriteAlarm(int txNum, bool state)
        {
            /************************************
             * Public function to write data to PLC
             ************************************/
            if (txNum == 6) g_PlcWriteData_b[Convert.ToInt32(B_Write.ErrorTx6)] = state;
            else if (txNum == 7) g_PlcWriteData_b[Convert.ToInt32(B_Write.ErrorTx7)] = state;
        }

        public RequestToTx PlcTxRequest(int txNum)
        {
            if (_modbusReadPending == false)
            {
                if (txNum == 6)
                {
                    return convertRequest(tx6Offset_i);
                }
                else if (txNum == 7)
                {
                    return convertRequest(tx7Offset_i);
                }
                else
                {
                    string errorTxt = string.Format("Tx number \"{0}\" not supported by PLC code!", txNum.ToString());
                    log.Write(errorTxt);
                    MessageBox.Show(errorTxt);
                    return null;
                }
            }
            else
            {
                //Data doesn't exist, or new data is coming soon
                return null;
            }
        }

        public void ChangeRequestToTx(int txNum, TxUsrCtrl.E_BtnType button, object Tag)
        {
            //Get current request values into override array
            for (int i = 0; i < g_PlcOverrideData_b.Length; i++)
            {
                g_PlcOverrideData_b[i] = g_PlcReadData_b[i + g_PlcWriteData_b.Length];
            }

            //Get location of the array to write
            int txDataOffset;
            if (txNum == 6)
            { txDataOffset = tx6Offset_i - g_PlcWriteData_b.Length; }
            else if (txNum == 7)
            { txDataOffset = tx7Offset_i - g_PlcWriteData_b.Length; }
            else
            {
                string errorTxt = string.Format("Tx number \"{0}\" not supported by PLC code!", txNum.ToString());
                log.Write(errorTxt);
                GuiText = errorTxt;
                MessageBox.Show(errorTxt);
                return;
            }

            //Change the value of the proper bits in the array according to button type
            switch (button)
            {
                case TxUsrCtrl.E_BtnType.Command:
                    {
                        //Convert CommandTx Request
                        bool heater = false;
                        bool general = false;
                        switch ((Thompson.Tx.E_CommandTx)Tag)
                        {
                            case Thompson.Tx.E_CommandTx.GeneralShutdown:
                                heater = false;
                                general = false;
                                break;
                            case Thompson.Tx.E_CommandTx.HeatingOn:
                                heater = true;
                                general = false;
                                break;
                            case Thompson.Tx.E_CommandTx.CircuitBreakerOn:
                                heater = false;
                                general = true;
                                break;
                            case Thompson.Tx.E_CommandTx.GeneralOn:
                                heater = true;
                                general = true;
                                break;
                            default:
                                break;
                        }
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.HeaterOn)] = heater;
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.GeneralOn)] = general;
                        break;
                    }
                case TxUsrCtrl.E_BtnType.Power:
                    {
                        //Convert Power Request
                        int pwr = Convert.ToInt32(Tag);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Pwr1)] = Convert.ToBoolean(pwr & 0x01);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Pwr2)] = Convert.ToBoolean(pwr & 0x02);
                        break;
                    }
                case TxUsrCtrl.E_BtnType.Modulation:
                    {
                        //Convert Modulation request
                        int mod = Convert.ToInt32(Tag);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Mod1)] = Convert.ToBoolean(mod & 1);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Mod2)] = Convert.ToBoolean(mod & 2);
                        break;
                    }
                case TxUsrCtrl.E_BtnType.Channel:
                    {
                        //Convert Channel request
                        int channel = Convert.ToInt32(Tag);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch1)] = Convert.ToBoolean(channel & 1);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch2)] = Convert.ToBoolean(channel & 2);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch4)] = Convert.ToBoolean(channel & 4);
                        g_PlcOverrideData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch8)] = Convert.ToBoolean(channel & 8);
                        break;
                    }
                default:
                    break;
            }

            //Request to write new data to PLC
            g_UserOverridesBurk_b = true;
        }

        private RequestToTx convertRequest(int txDataOffset)
        {

            //Private function that returns the current request for the selected transmitter
            RequestToTx request = new RequestToTx();

            //Set Channel
            request.Channel = 0;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch1)])
                request.Channel += 1;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch2)])
                request.Channel += 2;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch4)])
                request.Channel += 4;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Ch8)])
                request.Channel += 8;

            //Set Command

            /***************************************
             * !!!Command!!!
             * truth table (PLC Input Bits / Thompson Output Request):
             * b0 = Input HeaterOn
             * b1 = Input GeneralOn
             * | b1 | b0 |  Command
             * |----|----|-----------------
             * | 0  |  0 | GeneralShutdown
             * | 0  |  1 | HeatingOn
             * | 1  |  0 | CircuitBreakerOn
             * | 1  |  1 | GeneralOn
             ****************************************/
            int command = 0;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.HeaterOn)])
                command += 1;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.GeneralOn)])
                command += 2;
            switch (command)
            {
                case 0:
                    request.Command = Thompson.Tx.E_CommandTx.GeneralShutdown;
                    break;
                case 1:
                    request.Command = Thompson.Tx.E_CommandTx.HeatingOn;
                    break;
                case 2:
                    request.Command = Thompson.Tx.E_CommandTx.CircuitBreakerOn;
                    break;
                case 3:
                    request.Command = Thompson.Tx.E_CommandTx.GeneralOn;
                    break;
                default:
                    string errorTxt = "Error in interpreting PLC instuction! (TxPlcComm: convertRequest Command)";
                    log.Write(errorTxt);
                    GuiText = errorTxt;
                    _commEnabled = false;
                    break;
            }

            //Set Modulation
            int modulation = 0;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Mod1)])
                modulation += 1;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Mod2)])
                modulation += 2;
            request.ModulationRequest = modulation;

            //Set Power
            int power = 0;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Pwr1)])
                power += 1;
            if (g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.Pwr2)])
                power += 2;
            request.PowerRequest = power;

            //Set Modulation for Generator
            request.GenModulation = g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.GeneratorModulation)];

            //Set Power for Generator
            request.GenPower = g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.GeneratorPower)];

            //Set Heat On For Minimum Time
            request.HeatOnForMinTime = g_PlcReadData_b[txDataOffset + Convert.ToInt32(B_TxRead.HeatOnForMinimumTime)];

            return request;
        }

        private void connect()
        {
            // Avoid Continued Writing to the Log
            if (!_failedConnection)
            {
                log.Write(string.Format("Attempting to connect to PLC: {0}", PlcIpAddress));
            }

            try
            {
                // Don't Try to read until the port is open
                _modbusReadPending = true;
                // Create new modbus master and add event functions
                MBmaster = new Master(PlcIpAddress, 502);
                MBmaster.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData);
                MBmaster.OnException += new Master.ExceptionData(MBmaster_OnException);
                // Show additional fields, enable watchdog
                //Start continuous communication
                _modbusCommPortOpen = true;
                log.Write(string.Format("Connection with PLC successful."));
                _failedConnection = false;
                _modbusReadPending = false;
            }
            catch (SystemException error)
            {
                // Avoid Continued Writing to the Log
                if (!_failedConnection)
                {
                    log.Write(string.Format("Error connecting to PLC! {0}", error.Message));
                }
                GuiText = "PLC Communication Error. Check Log.";
                _modbusReadPending = false;
                _modbusCommPortOpen = false;
                _commEnabled = false;
                _failedConnection = true;
                OnPlcResponse(E_PlcEvent.Error1, GuiText);
            }
        }

        private Dictionary<int, RequestToTx> dummyRequests()
        {
            Dictionary<int, RequestToTx> requests = new Dictionary<int, RequestToTx>();
            requests.Add(6, new RequestToTx { Command = Thompson.Tx.E_CommandTx.GeneralOn, PowerRequest = 3, GenModulation = true, HeatOnForMinTime = true, ModulationRequest = 2, Channel = 11, GenPower = false });
            requests.Add(7, new RequestToTx { Command = Thompson.Tx.E_CommandTx.HeatingOn, PowerRequest = 1, GenModulation = false, HeatOnForMinTime = false, ModulationRequest = 0, Channel = 5, GenPower = true });
            return requests;
        }

        // ------------------------------------------------------------------------
        // Event for response data
        // ------------------------------------------------------------------------
        private void MBmaster_OnResponseData(ushort ID, byte function, byte[] values)
        {
            // Ignore watchdog response data
            if (ID == 0xFF) return;

            if (ID == C_ReadID)
            {
                /***************************************************/
                /*  Plc Read & Display data                        */
                /***************************************************/
                BitArray PlcRxData = new BitArray(values);
                PlcRxData.CopyTo(g_PlcReadData_b, 0);
                _modbusReadPending = false;
                GuiText = "PLC Communication working normally";
                //Dictionary<int, RequestToTx> requests = new Dictionary<int, RequestToTx>();
                _txPlcReadMutex.WaitOne();
                _requests.Clear();
                _requests.Add(6, convertRequest(tx6Offset_i));
                _requests.Add(7, convertRequest(tx7Offset_i));
                _txPlcReadMutex.ReleaseMutex();
                OnPlcResponse(E_PlcEvent.DataReceived, GuiText);
            }
        }

        // ------------------------------------------------------------------------
        // Modbus TCP slave exception
        // ------------------------------------------------------------------------
        private void MBmaster_OnException(ushort id, byte function, byte exception)
        {
            string exc = "PLC Error! Modbus: ";
            switch (exception)
            {
                case Master.excIllegalFunction: exc += "Illegal function!"; break;
                case Master.excIllegalDataAdr: exc += "Illegal data address!"; break;
                case Master.excIllegalDataVal: exc += "Illegal data value!"; break;
                case Master.excSlaveDeviceFailure: exc += "Slave device failure!"; break;
                case Master.excAck: exc += "Acknowledge!"; break;
                case Master.excSlaveIsBusy: exc += "Slave is busy!"; break;
                case Master.excGatePathUnavailable: exc += "Gateway path unavailable!"; break;
                case Master.excExceptionTimeout: exc += "Slave timed out!"; break;
                case Master.excExceptionConnectionLost: exc += "Connection is lost!"; break;
                case Master.excExceptionNotConnected: exc += "Not connected!"; break;
            }
            log.Write(exc + "Modbus slave exception");
            _commEnabled = false;
            _modbusCommPortOpen = false;
            _modbusReadPending = false;
            OnPlcResponse(E_PlcEvent.Error2, GuiText);
        }
    }


    class SwPlcComm : System.Windows.Forms.Form
    {
        // Mutex to keep PLC from accessing Data that is being written
        // by HMI and to keep HMI from reading Data which is being
        // updated by PLC
        public Mutex _txPlcWriteMutex = new Mutex();
        public Mutex _txPlcReadMutex = new Mutex();
        public string _guiText = "Initializing";
        public Color _guiTextForeColor = Color.Black;
        
        // Modbus Coils to Read Start at 3073
        private const int SwMatrixPlc_ReadCoilBaseAddress =             3073;
        private const int SwMatrixPlc_NumberOfReadCoils =               56;
        private const int SwMatrixPlc_WriteCoilBaseAddress =            3106;
        private const int SwMatrixPlc_NumberOfWriteCoils =              23;
        private const int SwMatrixPlc_ReadHoldingRegisterAddress =      24;
        private const int SwMatrixPlc_NumberOfReadHoldingRegisters =    2;
        private const int SwMatrixPlc_WriteHoldingRegisterAddress =     24;
        private const int SwMatrixPlc_NumberOfWriteHoldingRegisters =   2;

        // Data structures to support information read from or written to the PLC
        // These structures are kept separate with the intent to avoid accidental
        // overwriting of information from the user with information from the PLC
        private bool[] SwMatrixPlc_CoilsRead = new bool[SwMatrixPlc_NumberOfReadCoils];
        private bool[] SwMatrixPlc_CoilsToWrite = new bool[SwMatrixPlc_NumberOfWriteCoils];
        private Int16[] SwMatrixPlc_HoldingRegistersRead = new Int16[SwMatrixPlc_NumberOfReadHoldingRegisters];
        private Int16[] SwMatrixPlc_HoldingRegistersToWrite = new Int16[SwMatrixPlc_NumberOfWriteHoldingRegisters];


        // Allow Referencing registers by their function, rather than by their number.
        // These numbers are relative, but will directly index into the structure returned
        // from the Modbus Library (note: The Individual Coils are Populated directly into
        // the Status Array without need for an Enum).
        public enum ReadSwitchStatus
        {
            Available = 0,
            Valid = 11,
            Position = 22,
            DesiredPosition = 33,
            DropTransmitters = 44,
            HeartBeat = 55,
        }
        public enum E_PlcEvent
        {
            DataReceived = 0,
            Error1,
            Error2
        }
        public enum ControlSwitch
        {
            DesiredPosition = 0,
            DropTransmitters = 11,
            HeartBeat = 22,
        }
        private enum TimerControl
        {
            TransferRetryTimeout = 0,
            TransferAttemptTimeout = 1,
        }
        
        //Modbus variables
        private bool _commEnabled = false;
        private bool _commWasEnabled = false;
        private string _plcIpAddress;
        private bool _failedConnection = false;
        
        private ModbusTCP.Master SwMatrix;
        private bool _modbusCommPortOpen = false;
        private bool _modbusReadPending = false;
        private const ushort C_ReadID =  1;
        private const ushort C_WriteID = 2;
        private const ushort H_ReadID =  3;
        private const ushort H_WriteID = 4;
        
        //Create instance of log data for logging from this file
        AppMain.LogData log = AppMain.LogData.GetObject;
        
        //Create Timer for polling data from PLC
        public System.Threading.Timer tmrScanPLC;
        
        //Declaration for Application to respond to PLC data received
        private const int RetryTimeout = 5000;
        private int _scanTime;
        public delegate void PlcResponse(E_PlcEvent PlcEvent, string GuiText, Dictionary<int,RequestToTx> requests);
        //public event PlcResponse OnPlcResponse;

        public string GetStatusText()
        {
            return _guiText;
        }
        //Method called to start communication with the PLC, setting simple parameters
        public void StartCommunication(string IpAddress, int scanTime)
        {   //Set PLC communication parameters
            _plcIpAddress = IpAddress;
            //Create Timer for polling data from PLC
            tmrScanPLC = new System.Threading.Timer(tmrScanPLC_Tick,null,scanTime,scanTime);
            _scanTime = scanTime;
            //Enable cyclic read-write to PLC
            _commEnabled = true;
            _guiText = "Initializing Communications...";
        }

        //Cyclic timer fetch new data from PLC and write response
        void tmrScanPLC_Tick(object sender)
        {
            //If there was an error, decrease the update rate
            if (_commEnabled == false)
            {
                tmrScanPLC.Change(RetryTimeout, RetryTimeout);
            }
            else
            {
                if (!_commWasEnabled)
                {
                    tmrScanPLC.Change(_scanTime, _scanTime);
                }
            }
            _commWasEnabled = _commEnabled;
            //Open Modbus if not already connected
            if (!_modbusCommPortOpen)
            {
                // Note: This timer runs in a separate thread. There is no need for any
                // unique operations here.
                // keep this from connecting muliple ports
                if (!_modbusReadPending)
                {
                    connect();
                }
            }
            else
            {
                if (!SwMatrix.connected)
                {
                    _modbusCommPortOpen = false;
                }
                //Don't start if we're still waiting for a response
                if (_modbusReadPending == false)
                {
                    //run method ReadWrite() in a background thread
                    //System.Threading.Thread ReadWriteThread = new System.Threading.Thread(ReadWrite);
                    //ReadWriteThread.Start();
                    ReadWrite();
                }
            }
        }

        // Handle information reported back from the PLC. This should Read responses (valid data) and
        // Write responses (validity indication).
        private void OnPlcResponse(SwPlcComm.E_PlcEvent plcEvent, string guiText, Dictionary<int, RequestToTx> requests)
        {
            //Update display status
            _guiText = guiText;

            if (plcEvent != SwPlcComm.E_PlcEvent.DataReceived)
            {   //Set color for error
                _guiTextForeColor = Color.Red;
            }
            else
            {
                _guiTextForeColor = Color.Black;
                _guiText = "Successfully Communicating";
            }

        }


        //Master communication method which sends and receives data with PLC over Modbus
        private void ReadWrite()
        {
            //Send only one command per cycle
            // NOTE: this has been ammended because the system operates correctly if all
            // requests are send simultaneously
//               switch(SwMatrixPlc_CommState)
//               {
//                   case SwMatrixPlc_WriteCoils:
//                   {
                    //Toggle Heartbeat bit
                    SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ControlSwitch.HeartBeat)-1] = !SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadSwitchStatus.HeartBeat)-1];

                    //Write Coils
                    byte[] ByteArrayToWrite = new byte[SwMatrixPlc_NumberOfWriteCoils / 8 + 1];
                    BitArray bitArray1 = new BitArray(SwMatrixPlc_CoilsToWrite);
                    bitArray1.CopyTo(ByteArrayToWrite, 0);

                    //Write Coils using Modbus
                    SwMatrix.WriteMultipleCoils(C_WriteID, SwMatrixPlc_WriteCoilBaseAddress, (ushort)SwMatrixPlc_NumberOfWriteCoils, ByteArrayToWrite);
//                        break;
//                    }
//                    case SwMatrixPlc_ReadCoils:
//                    {

                    //Read Coils
                    SwMatrix.ReadCoils(C_ReadID, SwMatrixPlc_ReadCoilBaseAddress, SwMatrixPlc_NumberOfReadCoils);
                    _modbusReadPending = true; //Request is sent; new data is pending
//                        break;
//                    }
//                    case SwMatrixPlc_WriteHoldingRegisters:
//                    {
                    //Write Holding Registers
                    SwMatrix.ReadCoils(H_WriteID, SwMatrixPlc_WriteHoldingRegisterAddress, SwMatrixPlc_NumberOfWriteHoldingRegisters);
//                        break;
//                    }
//                    case SwMatrixPlc_ReadHoldingRegisters:
//                    {
                    //Read Holding Registers
                    SwMatrix.ReadHoldingRegister(H_ReadID, SwMatrixPlc_ReadHoldingRegisterAddress, SwMatrixPlc_NumberOfReadHoldingRegisters);
                    _modbusReadPending = true; //Request is sent; new data is pending
//                        break;
//                    }
//            }
        }


            private void connect()
            {
                if (!_failedConnection)
                {
                    log.Write(string.Format("Attempting to connect to PLC: {0}", _plcIpAddress));
                }

                // once a connection has been attempted, don't try it again
                try
                {
                    // make sure that the Read/Write function is not called
                    // until it is configured.
                    _modbusReadPending = true;
                    // Create new modbus master and add event functions
                    SwMatrix = new Master(_plcIpAddress, 502);
                    SwMatrix.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData);
                    SwMatrix.OnException += new Master.ExceptionData(MBmaster_OnException);
                    // Show additional fields, enable watchdog
                    //Start continuous communication
                    log.Write(string.Format("Connection with PLC successful."));
                    // Inform the world that the thread is open so that it doesn't continually retry
                    _modbusCommPortOpen = true;
                    _failedConnection = false;
                    _modbusReadPending = false;
                }
                catch (SystemException error)
                {
                    if (!_failedConnection)
                    {
                        log.Write(string.Format("Error connecting to PLC! {0}", error.Message));
                    }
                    _guiText = "PLC Communication Error. Check Log.";
                    _modbusReadPending = false;
                    _commEnabled = false;
                    OnPlcResponse(E_PlcEvent.Error1, _guiText, null);
                    _failedConnection = true;
                    _modbusCommPortOpen = false;
                }
            }

            // ------------------------------------------------------------------------
            // Event for response data
            // ------------------------------------------------------------------------
            private void MBmaster_OnResponseData(ushort ID, byte function, byte[] values)
            {
                // ------------------------------------------------------------------
                // Seperate calling threads
                if (this.InvokeRequired)
                {
                    this.BeginInvoke(new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData), new object[] { ID, function, values });
                    return;
                }

                switch(ID)
                {
                    default:
                    // ignore Data from Write requests
                    case C_WriteID:
                    case H_WriteID:
                    case 0xFF:
                    {
                        return;
                    }

                    // Coil Read Request
                    case C_ReadID:
                    {
                        BitArray PlcRxData = new BitArray(values);
                        PlcRxData.CopyTo(SwMatrixPlc_CoilsRead, 0);
                        _modbusReadPending = false;
                        _guiText = "Switch Matrix PLC Communication working normally";
                        break;
                    }
                    // Holding Register Read Request
                    case H_ReadID:
                    {
                        byte y = 0;
                        for( byte x = 0; x < (values.Length)/2; x++)
                        {
                            Int16 tempVar = values[y++];
                            tempVar *= 100;
                            tempVar += values[y++];
                            SwMatrixPlc_HoldingRegistersRead[x] = tempVar;
                        }
                        _modbusReadPending = false;
                        break;
                    }
                }
            }

            // ------------------------------------------------------------------------
            // Modbus TCP slave exception
            // ------------------------------------------------------------------------
            private void MBmaster_OnException(ushort id, byte function, byte exception)
            {
                // ------------------------------------------------------------------
                // Seperate calling threads
                if (this.InvokeRequired)
                {
                    this.BeginInvoke(new ModbusTCP.Master.ExceptionData(MBmaster_OnException), new object[] { id, function, exception });
                    return;
                }

                string exc = "Switch Matrix PLC Error! Modbus: ";
                switch (exception)
                {
                    case Master.excIllegalFunction: exc += "Illegal function!"; break;
                    case Master.excIllegalDataAdr: exc += "Illegal data adress!"; break;
                    case Master.excIllegalDataVal: exc += "Illegal data value!"; break;
                    case Master.excSlaveDeviceFailure: exc += "Slave device failure!"; break;
                    case Master.excAck: exc += "Acknowledge!"; break;
                    case Master.excSlaveIsBusy: exc += "Slave is busy!"; break;
                    case Master.excGatePathUnavailable: exc += "Gateway path unavailable!"; break;
                    case Master.excExceptionTimeout: exc += "Slave timed out!"; break;
                    case Master.excExceptionConnectionLost: exc += "Connection is lost!"; break;
                    case Master.excExceptionNotConnected: exc += "Not connected!"; break;
                }
                log.Write(exc + "Modbus slave exception");
                _commEnabled = false;
                _modbusCommPortOpen = false;
                OnPlcResponse(E_PlcEvent.Error2, _guiText, null);
            }


        }
    }
    

public class RequestToTx
{
    public int Channel { get; set; }
    public Thompson.Tx.E_CommandTx Command { get; set; }
    public int ModulationRequest { get; set; }
    public int PowerRequest { get; set; }
    public bool GenPower { get; set; }
    public bool GenModulation { get; set; }
    public bool HeatOnForMinTime { get; set; }
}